#ifndef UTIL_H
#define UTIL_H

#include <pistache/http.h>
#include <pistache/base64.h>
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/prettywriter.h>
#include <filesystem>
#include <sstream>

class Accept : public Pistache::Http::Header::Accept
{
public:
    explicit Accept(Pistache::Http::Mime::MediaType mime) : mime_(std::move(mime)) {}
    explicit Accept(std::string &&raw_mime_str) : Accept(Pistache::Http::Mime::MediaType(std::move(raw_mime_str))) {}
    explicit Accept(const std::string &raw_mime_str) : Accept(Pistache::Http::Mime::MediaType(raw_mime_str)) {}
    void parseRaw(const char *str, size_t len) override { mime_.parseRaw(str, len); };
    void write(std::ostream &os) const override { os << mime_.toString(); };
private:
    Pistache::Http::Mime::MediaType mime_;
};

class Timer
{
public:
    inline Timer() { gettimeofday(&s, nullptr); }
    inline long GetSec() { End(); return e.tv_sec - s.tv_sec; }
    inline long GetMSec() { End(); return (e.tv_sec - s.tv_sec) * 1000 + (e.tv_usec - s.tv_usec) / 1000; }

private:
    timeval s{}, e{};
    inline void End() { gettimeofday(&e, nullptr); }
};

inline std::string Base64Encode(const std::string& i)
{
    std::vector<std::byte> bytes((std::byte*)i.c_str(), (std::byte*)i.c_str() + i.size());
    Base64Encoder encoder(bytes);
    encoder.Encode();
    return encoder.GetBase64EncodedString();
}

struct SingleCell
{
    std::string Row;
    std::string Column;
    std::string Value;
};

inline std::string Base64Decode(const std::string& i)
{
    Base64Decoder decoder(i);
    decoder.Decode();
    auto row = decoder.GetRawDecodedData();
    return std::string((char*)row.data(), (char*)row.data() + row.size());
}

inline std::string GetJsonString(const rapidjson::Document& dom)
{
    auto buffer = rapidjson::StringBuffer();
    auto write = rapidjson::PrettyWriter<rapidjson::StringBuffer>(buffer);
    dom.Accept(write);
    return buffer.GetString();
}

inline std::string RemoveRedundancyChars(const std::string& s)
{
    std::stringstream ss;
    char leftCh = 0;
    for(const auto& i : s)
    {
        if (leftCh == 0)
        {
            switch (i)
            {
                case ' ':
                case '\n':
                case '\r':
                case '\t':
                    continue;
                    break;
                case '\'':
                case '\"':
                    leftCh = i;
                default:
                    break;
            }
//            if (i == ' ') continue;
//            if (i == '\n') continue;
//            if (i == '\r') continue;
//            if (i == '\t') continue;
//            if (i == '\'') leftCh = i;
//            if (i == '\"') leftCh = i;
        }
        else if (leftCh == i) leftCh = 0;
        ss << i;
    }
    return ss.str();
}

inline std::string ReplaceChar(const std::string& s, char originCh, char targetCh)
{
    std::string o = s;
    int index = 0;
    while( (index = o.find(originCh,index)) != std::string::npos)
        o[index] = targetCh;
    return o;
}

inline bool IsUint(const std::string& str)
{
    return std::all_of(str.cbegin(), str.cend(), [](char i){ return (i < '9' && i > '0'); });
}

inline bool StrEndsWith(const std::string &mainStr, const std::string &toMatch)
{
    if (mainStr.size() < toMatch.size())
        return false;
    return !mainStr.compare(mainStr.size() - toMatch.size(), toMatch.size(), toMatch);
}

inline bool StrEndsWith(const std::string &mainStr, char toMatch)
{
    if (mainStr.empty())
        return false;
    return (mainStr[mainStr.length() - 1] == toMatch);
}

class Application
{
public:
    static inline void SetArgs(int argc, char** argv)
    {
        auto applicationFileFs = std::filesystem::path(argv[0]);
        applicationPath = applicationFileFs.parent_path();
        applicationFile = applicationFileFs.string();
    }

    static inline const std::string& ApplicationPath() { return Application::applicationPath; }
    static inline const std::string& ApplicationFile() { return Application::applicationFile; }

private:
    Application() = delete;
    Application(const Application&) = delete;
    Application operator=(const Application&) = delete;
    ~Application() = delete;

    static inline std::string applicationPath;
    static inline std::string applicationFile;
};

enum Status : uint8_t
{
    Failed = 0,
    Successful = 1,
    Nonfatal = 2
};

class Url
{
public:
    inline static char dec2hexChar(int16_t n)
    {
        if ( 0 <= n && n <= 9 )
            return char( '0' + n );
        else if ( 10 <= n && n <= 15 )
            return char( 'A' + n - 10 );
        else
            return 0;
    }

    inline static int16_t hexChar2dec(char c)
    {
        if ( '0' <= c && c <= '9' )
            return ( c - '0' );
        else if ( 'a' <= c && c <= 'f' )
            return ( c - 'a' + 10 );
        else if ( 'A' <= c && c <= 'F' )
            return (c - 'A' + 10 );
        else
            return -1;
    }

    inline static std::string UrlEncode(const std::string &URL)
    {
        std::stringstream ss;
        for (char c : URL)
        {
            if (( '0'<=c && c<='9' ) || ( 'a'<=c && c<='z' ) || ( 'A'<=c && c<='Z' ) || c=='/' || c=='.')
            {
                ss << c;
            }
            else
            {
                int j = (int16_t)c;
                if ( j < 0 )
                {
                    j += 256;
                }
                int i1, i0;
                i1 = j / 16;
                i0 = j - i1*16;
                ss << '%';
                ss << dec2hexChar(i1);
                ss << dec2hexChar(i0);
            }
        }
        return ss.str();
    }

    inline static std::string URLDecode(const std::string &URL)
    {
        std::stringstream ss;
        for ( unsigned int i=0; i<URL.size(); i++ )
        {
            char c = URL[i];
            if ( c != '%' )
            {
                ss << c;
            }
            else
            {
                char c1 = URL[++i];
                char c0 = URL[++i];
                int num = 0;
                num += hexChar2dec(c1) * 16 + hexChar2dec(c0);
                ss << char(num);
            }
        }
        return ss.str();
    }
};


inline std::string RemoveFirstSubString(const std::string& inputStr, const std::string& subStr)
{
    auto n1 = inputStr.find(subStr);
    auto n2 = n1 + subStr.length();
    auto n3 = inputStr.length() - n2;
    return (inputStr.substr(0, n1) + inputStr.substr(n2, n3));
}

inline std::string RemoveSubString(const std::string& inputStr, const std::string& subStr)
{
    auto outputStr = inputStr;
    while(true)
    {
        auto n1 = outputStr.find(subStr);
        if (n1 == std::string::npos)
            break;
        auto n2 = n1 + subStr.length();
        auto n3 = outputStr.length() - n2;
        outputStr = (outputStr.substr(0, n1) + outputStr.substr(n2, n3));
    }
    return outputStr;
}

#endif //UTIL_H
